【八】【vlc

您所在的位置:网站首页 安卓 vlc 【八】【vlc

【八】【vlc

#【八】【vlc| 来源: 网络整理| 查看: 265

1、由前面章节分析过可知vout结构体信息对象初始化是在decoder层的CreateDecoder方法中:

// [vlc/src/input/decoder.c] // vout赋值初始化方法指针赋值 static decoder_t * CreateDecoder( vlc_object_t *p_parent, input_thread_t *p_input, const es_format_t *fmt, input_resource_t *p_resource, sout_instance_t *p_sout ) { // ... 省略部分代码 decoder_t *p_dec; // 此处赋值了aout/vout的初始化方法指针 /* Set buffers allocation callbacks for the decoders */ p_dec->pf_aout_format_update = aout_update_format; p_dec->pf_vout_format_update = vout_update_format; // 该方法实现:获取vout端图像队列中的buffer // 【注:该图像队列池缓冲区其实最终是从display展示层模块获取的】 // 此处暂不展开分析 TODO p_dec->pf_vout_buffer_new = vout_new_buffer; p_dec->pf_spu_buffer_new = spu_new_buffer; // ... 省略部分代码 } // [vlc/src/input/decoder.c] static int vout_update_format( decoder_t *p_dec ) {// ... 省略部分代码 decoder_owner_sys_t *p_owner = p_dec->p_owner; vout_thread_t *p_vout; // 【dpb_size】查阅资料可知是指解码图像缓冲区(DPB)大小,以缓存帧数为单位 unsigned dpb_size; switch( p_dec->fmt_in.i_codec ) { case VLC_CODEC_HEVC: case VLC_CODEC_H264: case VLC_CODEC_DIRAC: /* FIXME valid ? */ dpb_size = 18; break; case VLC_CODEC_VP5: case VLC_CODEC_VP6: case VLC_CODEC_VP6F: case VLC_CODEC_VP8: dpb_size = 3; break; default: dpb_size = 2; break; } p_vout = input_resource_RequestVout( p_owner->p_resource, p_vout, &fmt, dpb_size + p_dec->i_extra_picture_buffers + 1, true ); vlc_mutex_lock( &p_owner->lock ); p_owner->p_vout = p_vout; if( p_owner->p_input != NULL ) // vout输出端创建完成后发送创建成功事件如通知java层等 input_SendEventVout( p_owner->p_input ); // ... 省略部分代码 } // [vlc/src/input/resource.c] vout_thread_t *input_resource_RequestVout( input_resource_t *p_resource, vout_thread_t *p_vout, const video_format_t *p_fmt, unsigned dpb_size, bool b_recycle ) { vlc_mutex_lock( &p_resource->lock ); vout_thread_t *p_ret = RequestVout( p_resource, p_vout, p_fmt, dpb_size, b_recycle ); vlc_mutex_unlock( &p_resource->lock ); return p_ret; } // [vlc/src/input/resource.c] static vout_thread_t *RequestVout( input_resource_t *p_resource, vout_thread_t *p_vout, const video_format_t *p_fmt, unsigned dpb_size, bool b_recycle ) { vlc_assert_locked( &p_resource->lock ); if( !p_vout && !p_fmt ) { if( p_resource->p_vout_free ) { msg_Dbg( p_resource->p_vout_free, "destroying useless vout" ); vout_CloseAndRelease( p_resource->p_vout_free ); p_resource->p_vout_free = NULL; } return NULL; } if( p_fmt ) {// 视频格式信息不为空时 /* */ if( !p_vout && p_resource->p_vout_free ) {// 重新使用空闲的vout对象 msg_Dbg( p_resource->p_parent, "trying to reuse free vout" ); p_vout = p_resource->p_vout_free; p_resource->p_vout_free = NULL; } else if( p_vout ) { assert( p_vout != p_resource->p_vout_free ); vlc_mutex_lock( &p_resource->lock_hold ); // 若创建之前不为空则从vout输出端列表中删除旧vout对象 TAB_REMOVE( p_resource->i_vout, p_resource->pp_vout, p_vout ); vlc_mutex_unlock( &p_resource->lock_hold ); } /* */ vout_configuration_t cfg = { .vout = p_vout, .input = VLC_OBJECT(p_resource->p_input), .change_fmt = true, .fmt = p_fmt, .dpb_size = dpb_size, }; // 根据vout配置信息初始化vout输出端对象 // 见1.1小节分析 p_vout = vout_Request( p_resource->p_parent, &cfg ); if( !p_vout ) return NULL; // 从input输入对象信息中获取展示title信息并发送给vout输出端命令队列中处理 DisplayVoutTitle( p_resource, p_vout ); /* Send original viewpoint to the input in order to update other ESes */ if( p_resource->p_input != NULL ) input_Control( p_resource->p_input, INPUT_SET_INITIAL_VIEWPOINT, &p_fmt->pose ); vlc_mutex_lock( &p_resource->lock_hold ); // 添加到当前vout输出端列表中 TAB_APPEND( p_resource->i_vout, p_resource->pp_vout, p_vout ); vlc_mutex_unlock( &p_resource->lock_hold ); return p_vout; } else {// 视频格式信息为空时 assert( p_vout ); vlc_mutex_lock( &p_resource->lock_hold ); TAB_REMOVE( p_resource->i_vout, p_resource->pp_vout, p_vout ); const int i_vout_active = p_resource->i_vout; vlc_mutex_unlock( &p_resource->lock_hold ); if( p_resource->p_vout_free || i_vout_active > 0 || !b_recycle ) {// 回收摧毁当前vout对象,因为已经存在一个已激活保存的free空闲对象了 if( b_recycle ) msg_Dbg( p_resource->p_parent, "destroying vout (already one saved or active)" ); vout_CloseAndRelease( p_vout ); } else {// 刷新当前vout中的各种数据,转换为一个空闲对象并保存 msg_Dbg( p_resource->p_parent, "saving a free vout" ); vout_Flush( p_vout, 1 ); vout_FlushSubpictureChannel( p_vout, -1 ); vout_configuration_t cfg = { .vout = p_vout, .input = NULL, .change_fmt = false, .fmt = NULL, .dpb_size = 0, }; p_resource->p_vout_free = vout_Request( p_resource->p_parent, &cfg ); } return NULL; } }

1.1、vout_Request实现分析:

// 【vlc/src/video_output/video_output.c】 vout_thread_t *vout_Request(vlc_object_t *object, const vout_configuration_t *cfg) { vout_thread_t *vout = cfg->vout; if (cfg->change_fmt && !cfg->fmt) { if (vout) vout_CloseAndRelease(vout); return NULL; } // 若传入的vout存在则尝试复用即重新根据视频格式重新更新vout对象 /* If a vout is provided, try reusing it */ if (vout) { if (vout->p->input != cfg->input) { if (vout->p->input) spu_Attach(vout->p->spu, vout->p->input, false); vout->p->input = cfg->input; if (vout->p->input) spu_Attach(vout->p->spu, vout->p->input, true); } if (cfg->change_fmt) { vout_control_cmd_t cmd; vout_control_cmd_Init(&cmd, VOUT_CONTROL_REINIT); cmd.u.cfg = cfg; vout_control_Push(&vout->p->control, &cmd); vout_control_WaitEmpty(&vout->p->control); vout_IntfReinit(vout); } if (!vout->p->dead) { msg_Dbg(object, "reusing provided vout"); return vout; } vout_CloseAndRelease(vout); msg_Warn(object, "cannot reuse provided vout"); } // 如复用失败则根据当前配置信息重新创建新的vout对象 return VoutCreate(object, cfg); } // [vlc/src/video_output/video_output.c] static vout_thread_t *VoutCreate(vlc_object_t *object, const vout_configuration_t *cfg) { video_format_t original; if (VoutValidateFormat(&original, cfg->fmt)) return NULL; // 创建video out输出端初始化对象 /* Allocate descriptor */ vout_thread_t *vout = vlc_custom_create(object, sizeof(*vout) + sizeof(*vout->p), "video output"); if (!vout) { video_format_Clean(&original); return NULL; } /* */ vout->p = (vout_thread_sys_t*)&vout[1]; vout->p->original = original; // 解码图像缓冲区(DPB)大小,以缓存帧数为单位 vout->p->dpb_size = cfg->dpb_size; // 初始化vout控制指令列表数据 vout_control_Init(&vout->p->control); // 添加【VOUT_CONTROL_INIT】vout控制初始化指令到指令列表中 vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_INIT); // 初始化vout输出端图像显示统计状态值【原子性】即是否已成功显示和是否已丢失 // 该原子性值此前分析是 decoder层用于判断当前图像在vout输出端的输出结果 vout_statistic_Init(&vout->p->statistic); // 初始化快照图像信息结构体对象 vout_snapshot_Init(&vout->p->snapshot); /* Initialize locks */ vlc_mutex_init(&vout->p->filter.lock); vlc_mutex_init(&vout->p->spu_lock); // 初始化赋值一些“变量字段”和控制接口, // 功能选择的回调接口如变速【倍数】播放、【剪切】显示尺寸大小设置 /* Take care of some "interface/control" related initialisations */ vout_IntfInit(vout); // 初始化子图像单元信息对象,其实是对应的字幕显示信息 /* Initialize subpicture unit */ vout->p->spu = spu_Create(vout, vout); // title显示的配置 vout->p->title.show = var_InheritBool(vout, "video-title-show"); vout->p->title.timeout = var_InheritInteger(vout, "video-title-timeout"); vout->p->title.position = var_InheritInteger(vout, "video-title-position"); /* Get splitter name if present */ vout->p->splitter_name = var_InheritString(vout, "video-splitter"); // 隔行扫描信息处理 /* */ vout_InitInterlacingSupport(vout, vout->p->displayed.is_interlaced); /* Window */ if (vout->p->splitter_name == NULL) { vout_window_cfg_t wcfg = { .is_standalone = !var_InheritBool(vout, "embedded-video"), .is_fullscreen = var_GetBool(vout, "fullscreen"), .type = VOUT_WINDOW_TYPE_INVALID, // TODO: take pixel A/R, crop and zoom into account #ifdef __APPLE__ .x = var_InheritInteger(vout, "video-x"), .y = var_InheritInteger(vout, "video-y"), #endif .width = cfg->fmt->i_visible_width, .height = cfg->fmt->i_visible_height, }; vout_window_t *window = vout_display_window_New(vout, &wcfg); if (window != NULL) { if (var_InheritBool(vout, "video-wallpaper")) vout_window_SetState(window, VOUT_WINDOW_STATE_BELOW); else if (var_InheritBool(vout, "video-on-top")) vout_window_SetState(window, VOUT_WINDOW_STATE_ABOVE); } vout->p->window = window; } else vout->p->window = NULL; /* */ vlc_object_set_destructor(vout, VoutDestructor); // 重点:开启vout视频输出端工作线程,见第2小节分析 // 主要处理视频图像buffer接收和图像显示控制事件 /* */ if (vlc_clone(&vout->p->thread, Thread, vout, VLC_THREAD_PRIORITY_OUTPUT)) { if (vout->p->window != NULL) vout_display_window_Delete(vout->p->window); spu_Destroy(vout->p->spu); vlc_object_release(vout); return NULL; } // 工作线程执行正常则进行判断条件成立进入等待 // 【条件[while ((ctrl->cmd.i_size > 0 || ctrl->is_processing) && !ctrl->is_dead)] // 即当前命令列表中有命令或有命令正在执行中或当前指令列表未关闭则wait】 // [vlc_cond_wait(&ctrl->wait_acknowledge, &ctrl->lock)] vout_control_WaitEmpty(&vout->p->control); if (vout->p->dead) { msg_Err(vout, "video output creation failed"); vout_CloseAndRelease(vout); return NULL; } // 保存input输入端信息对象可进行交互 vout->p->input = cfg->input; if (vout->p->input) spu_Attach(vout->p->spu, vout->p->input, true); return vout; }

2、vout端工作线程Thead实现分析: [vlc/src/video_output/video_output.c]

/***************************************************************************** * Thread: video output thread ***************************************************************************** * Video output thread. This function does only returns when the thread is * terminated. It handles the pictures arriving in the video heap and the * display device events. *****************************************************************************/ static void *Thread(void *object) { vout_thread_t *vout = object; vout_thread_sys_t *sys = vout->p; mtime_t deadline = VLC_TS_INVALID; bool wait = false; for (;;) { vout_control_cmd_t cmd; if (wait) { // 默认最大100毫秒的延迟等待图像数据的延迟等待时间 const mtime_t max_deadline = mdate() + 100000; // 若此前已有延迟时间,那么则获取最小的那个时间点 deadline = deadline vlc_mutex_lock(&ctrl->lock); if (ctrl->cmd.i_size has_cmd = true; // 有指令任务存在,则执行指令列表中第一个指令任务即FIFO队列, // 获取指令后移除当前指令 *cmd = ARRAY_VAL(ctrl->cmd, 0); ARRAY_REMOVE(ctrl->cmd, 0); // 标记有指令正在执行中 ctrl->is_processing = true; } else { has_cmd = false; // 没有指令则标记下次循环时若无指令任务则允许可sleep当前vout线程 ctrl->can_sleep = true; } vlc_mutex_unlock(&ctrl->lock); return has_cmd ? VLC_SUCCESS : VLC_EGENERIC; }

2.2、ThreadControl实现分析:

// [vlc/src/video_output/video_output.c] static int ThreadControl(vout_thread_t *vout, vout_control_cmd_t cmd) { switch(cmd.type) { case VOUT_CONTROL_INIT: // 由前面分析可知,该指令任务在第1小节中vout初始化过程中有入栈过。 // 初始化vout一些状态:线程是否关闭、是否允许丢弃延迟帧、暂停时间、图像渲染时间估计信息 ThreadInit(vout); // 启动工作线程的一些初始化设置 // 见该小节后续分析 if (ThreadStart(vout, NULL)) { ThreadClean(vout); return 1; } break; case VOUT_CONTROL_CLEAN: ThreadStop(vout, NULL); ThreadClean(vout); return 1; case VOUT_CONTROL_REINIT: if (ThreadReinit(vout, cmd.u.cfg)) return 1; break; // ... 省略部分代码 } vout_control_cmd_Clean(&cmd); return 0; } // [vlc/src/video_output/video_output.c] static int ThreadStart(vout_thread_t *vout, vout_display_state_t *state) { vlc_mouse_Init(&vout->p->mouse); // 重点:初始化用于decoder层decode之后获取一个新图像FIFO队列, // 然后入栈decoded已解码图像到该队列中 vout->p->decoder_fifo = picture_fifo_New(); vout->p->decoder_pool = NULL; vout->p->display_pool = NULL; vout->p->private_pool = NULL; vout->p->filter.configuration = NULL; // 将解码器端的视频格式赋值给过滤器【滤镜】格式 video_format_Copy(&vout->p->filter.format, &vout->p->original); filter_owner_t owner = { .sys = vout, .video = { .buffer_new = VoutVideoFilterStaticNewPicture, }, }; // 初始化static视频滤镜链【过滤器链】filters,(过滤/转换) vout->p->filter.chain_static = filter_chain_NewVideo( vout, true, &owner ); // 重新赋值新图像对象buffer数据的方法指针,上一个赋值的值是交给了【chain_static】对象中 owner.video.buffer_new = VoutVideoFilterInteractiveNewPicture; // 然后再初始化filters交互过滤器链 vout->p->filter.chain_interactive = filter_chain_NewVideo( vout, true, &owner ); // 图像显示配置信息对象:显示的宽高、是否可自动缩放【is_display_filled】、显示位置等设置信息 vout_display_state_t state_default; if (!state) { VoutGetDisplayCfg(vout, &state_default.cfg, vout->p->display.title); #if defined(_WIN32) || defined(__OS2__) bool below = var_InheritBool(vout, "video-wallpaper"); bool above = var_InheritBool(vout, "video-on-top"); state_default.wm_state = below ? VOUT_WINDOW_STATE_BELOW : above ? VOUT_WINDOW_STATE_ABOVE : VOUT_WINDOW_STATE_NORMAL; #endif state_default.sar.num = 0; state_default.sar.den = 0; state = &state_default; } // 初始化加载视频图像显示模块或带上视频剪辑/分割模块 // 见display视频显示模块分析章节 TODO if (vout_OpenWrapper(vout, vout->p->splitter_name, state)) goto error; // 主要初始化vout中多个图像buffer缓冲池对象,其中display和decoder层的pool均从display层获取 // 见display视频显示模块分析章节 TODO // 注:【sys->display.use_dr】dr的大概意思是directly rendering直接渲染显示即不使用视频滤镜功能 if (vout_InitWrapper(vout)) { vout_CloseWrapper(vout, state); goto error; } assert(vout->p->decoder_pool && vout->p->private_pool); vout->p->displayed.current = NULL; vout->p->displayed.next = NULL; vout->p->displayed.decoded = NULL; vout->p->displayed.date = VLC_TS_INVALID; vout->p->displayed.timestamp = VLC_TS_INVALID; vout->p->displayed.is_interlaced = false; vout->p->step.last = VLC_TS_INVALID; vout->p->step.timestamp = VLC_TS_INVALID; vout->p->spu_blend_chroma = 0; vout->p->spu_blend = NULL; video_format_Print(VLC_OBJECT(vout), "original format", &vout->p->original); return VLC_SUCCESS; error: // ...省略部分代码 return VLC_EGENERIC; }

2.3、ThreadDisplayPicture实现分析:

// [vlc/src/video_output/video_output.c] static int ThreadDisplayPicture(vout_thread_t *vout, mtime_t *deadline) { bool frame_by_frame = !deadline; bool paused = vout->p->pause.is_on; bool first = !vout->p->displayed.current; if (first) // 若当前为第一个图像展示【即此前没有展示的图像】,则获取当前应该展示的图像(可能从decoder的FIFO图像队列中获取) // 内部处理:会判断当前图像是否大于最大的延迟阈值(默认为20毫秒) // 见2.3.1小节分析 if (ThreadDisplayPreparePicture(vout, true, frame_by_frame)) /* FIXME not sure it is ok */ return VLC_EGENERIC; if (!paused || frame_by_frame) while (!vout->p->displayed.next && !ThreadDisplayPreparePicture(vout, false, frame_by_frame)) ; const mtime_t date = mdate(); // 下一个图像渲染延迟显示的间隔时间估值:图像预估渲染时间+4毫秒 const mtime_t render_delay = vout_chrono_GetHigh(&vout->p->render) + VOUT_MWAIT_TOLERANCE; // 是否丢弃下一帧数据的处理 bool drop_next_frame = frame_by_frame; mtime_t date_next = VLC_TS_INVALID; if (!paused && vout->p->displayed.next) { // 预估下一个图像显示时间 date_next = vout->p->displayed.next->date - render_delay; if (date_next /* + 0 FIXME */ p->displayed.date > VLC_TS_INVALID) { // 图像展示刷新时间:当前PTS图像展示时间 + 80毫秒【默认两图像之间最大间隔时长】 - 【减去渲染延迟间隔时间估值】 date_refresh = vout->p->displayed.date + VOUT_REDISPLAY_DELAY - render_delay; // 若当前时间图像刷新时间小于等于当前时间,则表示当前待显示图像需要进行显示 refresh = date_refresh return VLC_EGENERIC; } if (drop_next_frame) { // 丢弃下一个图像数据 picture_Release(vout->p->displayed.current); vout->p->displayed.current = vout->p->displayed.next; vout->p->displayed.next = NULL; } if (!vout->p->displayed.current) return VLC_EGENERIC; // 立即显示当前图像 /* display the picture immediately */ bool is_forced = frame_by_frame || force_refresh || vout->p->displayed.current->b_force; // 图像展示渲染请求:主要有滤镜处理、字幕处理、图像裁切、最后传输到显示模块进行显示处理 // 见display显示模块章节分析 TODO int ret = ThreadDisplayRenderPicture(vout, is_forced); return force_refresh ? VLC_EGENERIC : ret; }

2.3.1、ThreadDisplayPreparePicture实现分析:

// [vlc/src/video_output/video_output.c] static int ThreadDisplayPreparePicture(vout_thread_t *vout, bool reuse, bool frame_by_frame) { // 是否应该丢弃延迟图像 bool is_late_dropped = vout->p->is_late_dropped && !vout->p->pause.is_on && !frame_by_frame; vlc_mutex_lock(&vout->p->filter.lock); // 尝试从视频滤镜器链中获取图像数据 picture_t *picture = filter_chain_VideoFilter(vout->p->filter.chain_static, NULL); assert(!reuse || !picture); while (!picture) { picture_t *decoded; if (reuse && vout->p->displayed.decoded) { // 重用当前保存的decoded已解码数据 decoded = picture_Hold(vout->p->displayed.decoded); } else { // 若为空,则从图像FIFO队列中获取第一个数据 decoded = picture_fifo_Pop(vout->p->decoder_fifo); if (decoded) { // 若获取到有图像数据 if (is_late_dropped && !decoded->b_force) { // 若允许丢弃延迟图像并且不是强制显示则进入此处 // 允许延迟显示时间阈值 mtime_t late_threshold; if (decoded->format.i_frame_rate && decoded->format.i_frame_rate_base) // 通过帧率来计算合适的延迟显示时间阈值 late_threshold = ((CLOCK_FREQ/2) * decoded->format.i_frame_rate_base) / decoded->format.i_frame_rate; else // 默认20毫秒延迟阈值 late_threshold = VOUT_DISPLAY_LATE_THRESHOLD; // 预测显示时间值【当前时间值】 const mtime_t predicted = mdate() + 0; /* TODO improve */ // 计算当前图像延迟时间:预测显示时间减去当前图像PTS显示时间 const mtime_t late = predicted - decoded->date; if (late > late_threshold) { // 若延迟时间大于允许的延迟阈值则丢弃该图像,进行下一个图像数据的获取 msg_Warn(vout, "picture is too late to be displayed (missing %"PRId64" ms)", late/1000); picture_Release(decoded); vout_statistic_AddLost(&vout->p->statistic, 1); continue; } else if (late > 0) { // 延迟时长 msg_Dbg(vout, "picture might be displayed late (missing %"PRId64" ms)", late/1000); } } if (!VideoFormatIsCropArEqual(&decoded->format, &vout->p->filter.format)) // 若当前图像格式和过滤器格式不同如宽高比等属性,则进行重置过滤器信息如更新过滤器相关的图像格式信息等 ThreadChangeFilters(vout, &decoded->format, vout->p->filter.configuration, -1, true); } } if (!decoded) break; reuse = false; if (vout->p->displayed.decoded) picture_Release(vout->p->displayed.decoded); // 赋值显示相关状态值 vout->p->displayed.decoded = picture_Hold(decoded); vout->p->displayed.timestamp = decoded->date; vout->p->displayed.is_interlaced = !decoded->b_progressive; // 从第一个视频滤镜器链中滤镜当前图像数据得到最终图像结果 picture = filter_chain_VideoFilter(vout->p->filter.chain_static, decoded); } vlc_mutex_unlock(&vout->p->filter.lock); if (!picture) return VLC_EGENERIC; assert(!vout->p->displayed.next); if (!vout->p->displayed.current) vout->p->displayed.current = picture; else vout->p->displayed.next = picture; return VLC_SUCCESS; }

2.4、vout_ManageWrapper实现分析:

// [vlc/src/video_output/vout_wrapper.c] void vout_ManageWrapper(vout_thread_t *vout) { vout_thread_sys_t *sys = vout->p; vout_display_t *vd = sys->display.vd; // true为当前display图像展示模块标记了当前图像池buffer数据无效 bool reset_display_pool = vout_AreDisplayPicturesInvalid(vd); // 管理display图像显示模块的一些配置 reset_display_pool |= vout_ManageDisplay(vd, !sys->display.use_dr || reset_display_pool); if (reset_display_pool) { // true为展示模块有过滤器链的使用 sys->display.use_dr = !vout_IsDisplayFiltered(vd); NoDrInit(vout); } } // [vlc/src/video_output/vout_wrapper.c] static void NoDrInit(vout_thread_t *vout) { vout_thread_sys_t *sys = vout->p; // 若为true则从display层获取展示图像池buffer缓冲区对象,否则初始化为空 if (sys->display.use_dr) sys->display_pool = vout_display_Pool(sys->display.vd, 3); else sys->display_pool = NULL; }

3、由上面节分析过可知vout结构体信息对象初始化方法为【vout_update_format】,而该方法通过调用分析可知,在如下方法中调用:

// 【vlc/include/vlc_codec.c】 VLC_USED static inline int decoder_UpdateVideoFormat( decoder_t *dec ) { assert( dec->fmt_in.i_cat == VIDEO_ES ); if( dec->fmt_in.i_cat == VIDEO_ES && dec->pf_vout_format_update != NULL ) return dec->pf_vout_format_update( dec ); else return -1; } // 而该方法被调用地方主要在: // 【lavc_UpdateVideoFormat】和【DecodeSidedata】方法中, // 【主要】均在decoder层的解码流程中解码方法【DecodeBlock】中被调用的。

由此此前第五六章分析中涉及vout输出端的TODO部分均已基本分析完成了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3